using System;
using System.Collections;
using UnityEngine;

namespace Voodoo.Utils
{
	public class Delay : MonoBehaviour
	{
		public Action<float> OnDelayRunning;

		public Action OnDelayCompleted;

		public Action OnDelaySkipped;

		public Action OnDelayCancelled;

		private Func<bool> _skipAction;

		private Func<bool> _cancelAction;

		protected float _delay;

		private float _startTime;

		private float _elapsedTime;

		private IEnumerator _timer;

		private TimeScale _scaleTime = TimeScale.Unscaled;

		private DelayState _state = DelayState.WaitToStart;

		public DelayState State
		{
			get
			{
				return _state;
			}
			private set
			{
				_state = value;
				OnStateChanged(_state);
			}
		}

		private float CurrentTime
		{
			get
			{
				if (_scaleTime == TimeScale.Scaled)
				{
					return Time.fixedTime;
				}
				return Time.fixedUnscaledTime;
			}
		}

		private float Interval { get; set; } = 1f;

		private float ElapsedTime => CurrentTime - _startTime;

		private float TotalElapsedTime => CurrentTime - _startTime + _elapsedTime;

		private float RemainingTime => _delay - TotalElapsedTime;

		public Delay Timescale(TimeScale timeScale)
		{
			if (State == DelayState.Running)
			{
				Debug.LogError("Can't modify Delay's timescale while timer is running.");
			}
			else
			{
				_scaleTime = timeScale;
			}
			return this;
		}

		public Delay Frequency(float frequency)
		{
			Interval = frequency;
			return this;
		}

		public Delay SetDelay(float delay)
		{
			_delay = delay;
			return this;
		}

		public Delay SkipIf(Func<bool> predicate)
		{
			_skipAction = predicate;
			return this;
		}

		public Delay CancelIf(Func<bool> predicate)
		{
			_cancelAction = predicate;
			return this;
		}

		[ContextMenu("Begin")]
		public Delay Begin()
		{
			if (State == DelayState.WaitToStart || State == DelayState.Paused)
			{
				State = DelayState.Running;
				_timer = Timer();
				StartCoroutine(_timer);
			}
			else
			{
				Debug.LogError("Delayer can't start (it's either ended or already running)");
			}
			return this;
		}

		[ContextMenu("Skip")]
		public Delay Skip()
		{
			StopCoroutine(_timer);
			OnDelaySkipped?.Invoke();
			OnTimeOut();
			return this;
		}

		[ContextMenu("Cancel")]
		public void Cancel()
		{
			StopCoroutine(_timer);
			OnDelayCancelled?.Invoke();
			UnityEngine.Object.Destroy(base.gameObject);
		}

		[ContextMenu("Pause")]
		public Delay Pause()
		{
			State = DelayState.Paused;
			_elapsedTime += ElapsedTime;
			StopCoroutine(_timer);
			return this;
		}

		[ContextMenu("Resume")]
		public Delay Resume()
		{
			if (State == DelayState.Paused)
			{
				Begin();
			}
			else
			{
				Debug.LogError("Delayer can't resume, it's not paused)");
			}
			return this;
		}

		public void End()
		{
			State = DelayState.End;
			StopCoroutine(_timer);
		}

		private void Update()
		{
			if (_cancelAction != null || _skipAction != null)
			{
				Func<bool> skipAction = _skipAction;
				if (skipAction != null && skipAction())
				{
					Skip();
				}
				Func<bool> cancelAction = _cancelAction;
				if (cancelAction != null && cancelAction())
				{
					Cancel();
				}
			}
		}

		private void SetStartTime()
		{
			_startTime = CurrentTime;
		}

		private IEnumerator Timer()
		{
			SetStartTime();
			while (RemainingTime > 0f)
			{
				base.gameObject.name = "Delayer Running : " + RemainingTime;
				OnDelayRunning?.Invoke(RemainingTime);
				switch (_scaleTime)
				{
				case TimeScale.Scaled:
					yield return new WaitForSeconds(Interval);
					break;
				case TimeScale.Unscaled:
					yield return new WaitForSecondsRealtime(Interval);
					break;
				}
			}
			OnTimeOut();
			yield return null;
		}

		private void DisplayDelayerName(DelayState state)
		{
			switch (state)
			{
			case DelayState.Paused:
				base.gameObject.name = "Delayer Paused At : " + RemainingTime;
				break;
			case DelayState.Undefined:
				base.gameObject.name = "Delayer Pending";
				break;
			case DelayState.WaitToStart:
				base.gameObject.name = "Delayer Pending";
				break;
			case DelayState.End:
				base.gameObject.name = "Delayer Ended";
				break;
			case DelayState.Running:
				break;
			}
		}

		private void OnStateChanged(DelayState state)
		{
			DisplayDelayerName(state);
		}

		protected virtual void OnTimeOut()
		{
			End();
			OnDelayCompleted?.Invoke();
			UnityEngine.Object.Destroy(base.gameObject);
		}
	}
}
